﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Alex_s_Dream_The_Toy_World
{
    public class AIComponent: BaseComponent
    {
        private List<Node> closedList;
        private List<Node> openList;

        private Map currentMap;

        public Map CurrentMap
        {
          get { return currentMap; }
          set { currentMap = value; }
        }

        int weighted = 0;

        private List<Point> offsetPointList = new List<Point>() 
        {
            new Point(-1, 0),
            new Point(-1, 1),
            new Point(-1, -1),
            new Point(0, 1),
            new Point(0, -1),
            new Point(1, 0),
            new Point(1, 1),
            new Point(1, -1)
        };

        public AIComponent(BaseEntity parent)
            : base(parent)
        {
            ActivateComponent();

            closedList = new List<Node>();
            openList = new List<Node>();
            weighted = 1;
        }

        public Node FindPath(Point startPoint, Point goalPoint)
        {
            if (!ValidCoordinates(startPoint) || !ValidCoordinates(goalPoint))
            {
                //start and goal point are not valid
                return null;
            }
            closedList.Clear();
            openList.Clear();
            //Add the starting node to the open list. 
            Node currentNode = new Node(startPoint, null, 0, EstimateMovementCost(startPoint, goalPoint));
            openList.Add(currentNode);
            List<Node> neighborNodeList = new List<Node>();
            bool findPath = false;

            // While OpenList is not empty
            while(openList.Count != 0)
            {
                // Node that have min F cost
                Node minFNode = null;
                //Look for the lowest F cost square on the open list. We refer to this as the current square.
                foreach (Node node in openList)
                {
                    if (minFNode == null)
                    {
                        minFNode = node;
                    }
                    else
                    {
                        if (node.F < minFNode.F)
                        {
                            minFNode = node;
                        }
                    }
                }
                currentNode = minFNode;
                if (currentNode.Position == goalPoint)
                {
                    findPath = true;
                    break;
                }

                //Switch it to the closed list. 
                openList.Remove(currentNode);
                closedList.Add(currentNode);

                neighborNodeList.Clear();

                foreach (Point offsetPoint in offsetPointList)
                {
                    Point neighborPoint = new Point(currentNode.Position.X + offsetPoint.X, currentNode.Position.Y + offsetPoint.Y);
                    if (ValidCoordinates(neighborPoint) && 
                        IsWalkable(neighborPoint) && 
                        !IsInClosedList(new Node(neighborPoint)))
                    {
                        //add a valid adjacent square
                        neighborNodeList.Add(new Node(neighborPoint, currentNode, GetMovementCost(currentNode.Position, neighborPoint), EstimateMovementCost(neighborPoint, goalPoint)));
                    }
                }


                foreach (Node neighborNode in neighborNodeList)
                {
                    if (!IsInOpenList(neighborNode))
                    {
                        // If it isn’t on the open list, add it to the open list
                        openList.Add(neighborNode);
                    }
                    else
                    {
                        //If it is on the open list already
                        //Check to see if this path to that square is better, using G cost as the measure
                        if (neighborNode.G < currentNode.G)
                        {
                            //change the parent of the square to the current square
                            neighborNode.Parent = currentNode;

                            // recalculate the G and F scores of the square
                            neighborNode.G += currentNode.G;
                            neighborNode.F = neighborNode.G + neighborNode.H;
                        }
                    }
                }
            }

            // Return Result
            if (findPath)
                return ReconstructPath(currentNode);
            else
                return null;
        }

        private Node ReconstructPath(Node currentNode)
        {
            if (currentNode.Parent != null)
            {
                currentNode.Parent.NextNode = currentNode;
                return ReconstructPath(currentNode.Parent);
            }
            else
            {
                return currentNode;
            }
        }

        public virtual bool IsWalkable(Point point)
        {
            bool walkable = false;
            switch (currentMap.PointList[point.X, point.Y])
            {
                case TerrainPoint.GROUND:
                    walkable = true;
                    break;
                default:
                    walkable = false;
                    break;
            }
            return walkable;
        }

        private int EstimateMovementCost(Point current, Point goal)
        {
            return weighted * ManhattanMethod(current, goal);
        }

        private int GetMovementCost(Point current, Point goal)
        {
            int xOffset = Math.Abs(current.X - goal.X);
            int yOffset = Math.Abs(current.Y - goal.Y);

            if (xOffset < yOffset)
            {
                return xOffset * 14 + (yOffset - xOffset) * 10;
            }
            else
            {
                return yOffset * 14 + (xOffset - yOffset) * 10;
            }
            //return DiagonalShortcut(current, goal);
        }

        private bool IsVerticalDirection(Point directionPoint)
        {
            if (directionPoint.X == 0)
                return true;
            else
                return false;
        }

        private bool IsHorizontalDirection(Point directionPoint)
        {
            if (directionPoint.Y == 0)
                return true;
            else
                return false;
        }

        private bool IsDiagonalDirection(Point directionPoint)
        {
            if (directionPoint.Y != 0 && directionPoint.X != 0)
                return true;
            else
                return false;
        }

        private bool ValidCoordinates(Point point)
        {
            int x = point.X;
            int y = point.Y;
            /*
             * 
             * Our coordinates are constrained between 0 and MapWidth, MapHeight.
             * 
             * */
            if (x < 0)
            {
                return false;
            }
            if (y < 0)
            {
                return false;
            }
            if (x >= CurrentMap.Size)
            {
                return false;
            }
            if (y >= CurrentMap.Size)
            {
                return false;
            }
            return true;
        }

        private int ManhattanMethod(Point current, Point goal)
        {
            return 10 * (Math.Abs(current.X - goal.X) + Math.Abs(current.Y - goal.Y));
        }

        private int DiagonalShortcut(Point current, Point goal)
        {
            int h = 0;

            int xDistance = Math.Abs(current.X - goal.X);
            int yDistance = Math.Abs(current.Y - goal.Y);

            if (xDistance > yDistance)
            {
                h = 14 * yDistance + (10 * (xDistance - yDistance));
            }
            else
            {
                h = 14 * xDistance + (10 * (yDistance - xDistance));
            }


            return h;

        }

        private bool IsInOpenList(Node nodeTest)
        {

            bool exists = false;

            foreach (Node node in openList)
            {
                if (node.Position == nodeTest.Position)
                {
                    exists = true;
                    break;
                }
            }

            return exists;

        }

        private bool IsInClosedList(Node nodeTest)
        {

            bool exists = false;

            foreach (Node node in closedList)
            {
                if (node.Position == nodeTest.Position)
                {
                    exists = true;
                    break;
                }
            }

            return exists;

        }

        /// <summary>
        /// Handles a message sent to this component.
        /// </summary>
        /// <param name="message">Message to be handled</param>
        /// <returns>True, if handled, otherwise false</returns>
        public override bool ExecuteMessage(IMessage message)
        {
            return false;
        }

    } 
}
